#ifndef MALODY_TIMEUTIL_H_
#define MALODY_TIMEUTIL_H_

#include "MacroUtil.h"
#include "macro.h"
/*
 * Realtime clock
 *        System-wide clock that measures real (i.e., wall-clock) time.
 *        Setting this clock requires appropriate privileges.  This
 *        clock is affected by discontinuous jumps in the system time
 *        (e.g., if the system administrator manually changes the
 *        clock), and by the incremental adjustments performed by
 *        adjtime(3) and NTP.
 *
 * Monotonic clock
 *        Clock that cannot be set and represents monotonic time
 *        since some unspecified starting point.  This clock is
 *        not affected by discontinuous jumps in the system time
 *        (e.g., if the system administrator manually changes the
 *        clock), but is affected by the incremental adjustments
 *        performed by adjtime(3) and NTP.
 */

#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
#include <WinBase.h>
typedef FILETIME MRealtimeClockType;
typedef LARGE_INTEGER MMonotonicClockType;
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
#include <sys/time.h>
#include <mach/mach_time.h>
typedef struct timeval MRealtimeClockType;
typedef uint64_t MMonotonicClockType;
#else
#include <time.h>
typedef struct timespec MRealtimeClockType;
typedef struct timespec MMonotonicClockType;
#endif

struct ISOTime {
    int year;
    int month;
    int day;
    int wyear;
    int week;
    int wday;
    int yday;
    int hour;
    int min;
    int sec;
    int nsec;
    int tzmin;
};


extern MRealtimeClockType g_launchTime;
extern MMonotonicClockType g_launchMonotonicTime;

#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
extern LARGE_INTEGER g_monotonicTimerFreq;
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
extern mach_timebase_info_data_t g_monotonicTimerFreq;
#else
#endif

// This should be called before using launch time or calling any of get diff time functions
// This is called in AppDelegate::AppDelegate()
GlobalFlag initTimeUtil();


inline GlobalFlag getRealtimeClock(MRealtimeClockType & t) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
    GetSystemTimeAsFileTime(&t);
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    if (gettimeofday(&t, nullptr) != 0) {
        return GlobalFlag_CommonError;
    }
#else
    if (clock_gettime(CLOCK_REALTIME, &t) != 0) {
        return GlobalFlag_CommonError;
    }
#endif
    return GlobalFlag_Success;
}

inline GlobalFlag getMonotonicClock(MMonotonicClockType & t) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
    if (!QueryPerformanceCounter(&t)) {
        return GlobalFlag_CommonError;
    }
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    t = mach_absolute_time();
#else
    if (clock_gettime(CLOCK_MONOTONIC, &t) != 0) {
        return GlobalFlag_CommonError;
    }
#endif
    return GlobalFlag_Success;
}

inline double getRealtimeDiffTime(const MRealtimeClockType & endTime, const MRealtimeClockType & startTime) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
    if (endTime.dwHighDateTime >= 0x80000000UL || startTime.dwHighDateTime >= 0x80000000UL) {
        return 0;
    }
    // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284.aspx for why we should do this
    ULARGE_INTEGER end;
    ULARGE_INTEGER start;
    end.HighPart = endTime.dwHighDateTime;
    end.LowPart = endTime.dwLowDateTime;
    start.HighPart = startTime.dwHighDateTime;
    start.LowPart = startTime.dwLowDateTime;
    if (end.QuadPart >= start.QuadPart) {
        return static_cast<double>(end.QuadPart - start.QuadPart) / 1e+7;
    } else {
        return static_cast<double>(start.QuadPart - end.QuadPart) / -1e+7;
    }
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    return static_cast<double>(endTime.tv_sec - startTime.tv_sec)
        + (static_cast<double>(endTime.tv_usec - startTime.tv_usec)) / 1e+6;
#else
    return static_cast<double>(endTime.tv_sec - startTime.tv_sec)
        + (static_cast<double>(endTime.tv_nsec - startTime.tv_nsec)) / 1e+9;
#endif
}

inline double getMonotonicDiffTime(const MMonotonicClockType & endTime, const MMonotonicClockType & startTime) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
    return static_cast<double>(endTime.QuadPart - startTime.QuadPart) / static_cast<double>(g_monotonicTimerFreq.QuadPart);
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    return static_cast<double>(endTime - startTime) * static_cast<double>(g_monotonicTimerFreq.numer)
        / (static_cast<double>(g_monotonicTimerFreq.denom) * 1e+9);
#else
    return static_cast<double>(endTime.tv_sec - startTime.tv_sec)
        + (static_cast<double>(endTime.tv_nsec - startTime.tv_nsec)) / 1e+9;
#endif
}


GlobalFlag extractRealtimeToUTC(const MRealtimeClockType & t, ISOTime & dateTime);
GlobalFlag extractRealtimeToLocalTime(const MRealtimeClockType & t, ISOTime & dateTime);
// 这个是用来给strftime显示用的，不要用来转回time_t
// tm_isdst永远填-1，因此也不要在strftime里用%Z等输出
void convertToTm(const ISOTime & dateTime, struct tm & tms);


#endif
